/****************************************************************************
**  Tite Interactive Text Engine (TiTE)
**  Copyright (C) 2010  Eisoft Group
**
**  This file is part of TiTE.
**
**  TiTE is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  TiTE is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with TiTE.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/

/*!
 * \file Log_.hpp
 *
 * \date 01/10/2011
 */

#ifndef TITE_CORE_LOG__HPP
#define TITE_CORE_LOG__HPP

#include <algorithm>
#include <ctime>
#include <string>
#include <vector>

#include <tite/core/log/ILogListener.hpp>

/*!
 * \namespace tite
 *
 * \brief The TiTE namespace.
 */
namespace tite
{
    /*!
     * \namespace core
     *
     * \brief The TiTE core namespace.
     */
    namespace core
    {
        /*!
         * \class Log_ Log_.hpp
         *
         * \brief A simple, efficient generic logging class. 
         *
         * \note The Container template parameter expects an STL-style container.
         *
         * \author Jason Omahen
         * \version 0.02, 12/18/2010
         */
        template<
            typename T, 
            typename Listener = ILogListener,
            template <typename, typename> class Container = stdext::vector, 
            typename Allocator = stdext::allocator<T> 
        >
        class Log_
        {
            // Log_ helper functions to provide a simple interface
            friend Log_& log_I(void);
    #if defined(DEBUG) || defined(_DEBUG)
            friend Log_& log_D(void);
    #endif
            friend Log_& log_W(void);
            friend Log_& log_C(void);
            friend Log_& log_F(void);
        
        public:
            typedef T log_elem_t;
            typedef Listener listener_t;
            typedef listener_t* listener_ptr_t;
            typedef listener_t *const listener_const_ptr_t;

            Log_(void);
            ~Log_(void);

            void log(const stdext::string & message, log_levels::LogLevel messageLevel);

            void addListener(listener_const_ptr_t pLogListener);
            void removeListener(listener_const_ptr_t pLogListener);

            template<typename U> 
                Log_& operator<<(const U & u);

        private:
            char* _getLocalTime(void);
            void _prepare(log_levels::LogLevel l);
            void _writeToBuffer(const stdext::string & message);

            std::time_t m_rawTime;
            log_levels::LogLevel m_currentLogLevel;

            Container<T, Allocator> m_logBuffer;
            Container<listener_ptr_t, Allocator> m_logListeners;
        };
    
        /*!
         * \brief Log_ ctor.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        Log_<T, Listener, Container, Allocator>::Log_(void)
            : m_currentLogLevel(log_levels::None)
        {
            log("[Log Singleton] Starting up ...\n", log_levels::Info);
        }

        /*!
         * \brief Log_ dtor.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        Log_<T, Listener, Container, Allocator>::~Log_(void)
        {
            log("[Log Singleton] Shutting down ...\n", log_levels::Warning);

            for (Container<listener_ptr_t, Allocator>::iterator it = m_logListeners.begin(); it != m_logListeners.end(); ++it)
                safe_delete(&(*it));
        }
    
        /*!
         * \brief Logs a message at the specified log level.
         *
         * \param message       Contents to log.
         * \param messageLevel  Message type.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        void Log_<T, Listener, Container, Allocator>::log(const std::string & message, log_levels::LogLevel messageLevel)
        {
            _prepare(messageLevel);
            _writeToBuffer(message);
        }

        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        void Log_<T, Listener, Container, Allocator>::addListener(typename Log_<T, Listener, Container, Allocator>::listener_const_ptr_t pLogListener)
        {
            if (pLogListener)
                m_logListeners.push_back(pLogListener);
        }

        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        void Log_<T, Listener, Container, Allocator>::removeListener(typename Log_<T, Listener, Container, Allocator>::listener_const_ptr_t pLogListener)
        {
            m_logListeners.erase(stdext::remove(m_logListeners.begin(), 
                                                m_logListeners.end(), 
                                                pLogListener), 
                                 m_logListeners.end());
        }

        /*!
         * \brief Overloaded "stream" insertion operator; writes contents directly
         *        to the log buffer.
         *
         * \note Use in conjunction with the log_X helper functions for increased 
         *       clarity and simplicity.
         *
         * \param u  std::string-convertable message to log.
         *
         * \return Modified Log_ object after message was logged.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
            template<typename U>
            Log_<T, Listener, Container, Allocator>& Log_<T, Listener, Container, Allocator>::operator<<(const U & u)
            {
                _writeToBuffer(u);
                return *this;
            }

        /*!
         * \brief Formats the computer's local date/time into an ASCII string.
         *
         * \return The local date/time.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        char* Log_<T, Listener, Container, Allocator>::_getLocalTime(void)
        {
            std::time(&m_rawTime);
            return std::asctime(std::localtime(&m_rawTime));
        }

        /*!
         * \brief Prepares the logging buffer for a specific LogLevel.
         *
         * \param l  Logging level.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        void Log_<T, Listener, Container, Allocator>::_prepare(log_levels::LogLevel l)
        {
            stdext::string header, time(_getLocalTime());
        
            // Remove \n character from the time string
            time.pop_back();

            switch (l)
            {
            case log_levels::Info:
                header = "[INFO.]";
                break;
            case log_levels::Debug:
                header = "[DEBUG]";
                break;
            case log_levels::Warning:
                header = "[WARN!]";
                break;
            case log_levels::Critical:
            case log_levels::Fatal:
                header = "[FATAL]";
                break;
            }
            header += "[" + time + "]  ";

            // Write the prefix message
            m_currentLogLevel = l;
            _writeToBuffer(header);
        }

        /*!
         * \brief Commits the message to the internal logging buffer.
         *
         * \param message  Contents to log.
         */
        template<
            typename T, 
            typename Listener,
            template <typename, typename> class Container, 
            typename Allocator
        >
        void Log_<T, Listener, Container, Allocator>::_writeToBuffer(const stdext::string & message)
        {
            // A buffer may be completely unnecessary now... waste memory for already processed
            // messages? Eh... let's rethink this!
            m_logBuffer.insert(m_logBuffer.end(), message.begin(), message.end());
            stdext::for_each(m_logListeners.begin(), 
                             m_logListeners.end(), 
                             stdext::bind(&Listener::logMessage, 
                                          stdext::placeholders::_1, 
                                          stdext::cref(message), 
                                          m_currentLogLevel));
        }
    }// End of core namespace
}// End of tite namespace

#endif /* TITE_CORE_LOG__HPP */